#!/usr/bin/env python
from __future__ import division
from math import sqrt
import collections

class Vector(object):
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def __len__(self):
        return sqrt(Dot(self, self))

    def __getitem__(self, key):
        if key < 0:
            key = 3 + key # total accessible members + (-key)
        if key == 0:
            return self.x
        elif key == 1:
            return self.y
        elif key == 2:
            return self.z
        else:
            raise IndexError

    def __setitem__(self, key, value):
        if key < 0:
            key = 3 + key # total accessible members + (-key)
        if key == 0:
            self.x = value
        elif key == 1:
            self.y = value
        elif key == 2:
            self.z = value
        else:
            raise IndexError

    def __iter__(self):
        yield self.x
        yield self.y
        yield self.z
        raise StopIteration

    def __contains__(self, item):
        if self.x == item or self.y == item or self.z == item:
            return True
        else:
            return False

    def __add__(self, other):
        other = _toVector(other)
        return Vector(self.x+other.x, self.y+other.y, self.z+other.z)

    def __sub__(self, other):
        other = _toVector(other)
        return Vector(self.x-other.x, self.y-other.y, self.z-other.z)

    def __mul__(self, other):
        other = _toVector(other)
        return Vector(self.x*other.x, self.y*other.y, self.z*other.z)

    def __floordiv__(self, other):
        other = _toVector(other)
        return Vector(self.x // other.x, self.y // other.y, self.z // other.z)

    def __mod__(self, other):
        other = _toVector(other)
        return Vector(self.x % other.x, self.y % other.y, self.z % other.z)

    def __pow__(self, power, modulo=None):
        return Vector(pow(self.x, power, modulo), pow(self.y, power, modulo), pow(self.z, power, modulo))

    def __div__(self, other):
        other = _toVector(other, 1)
        return Vector(self.x // other.x, self.y // other.y, self.z // other.z)

    def __truediv__(self, other):
        other = _toVector(other, 1)
        return Vector(self.x / other.x, self.y / other.y, self.z / other.z)

    def __iadd__(self, other):
        other = _toVector(other)
        self.x += other.x
        self.y += other.y
        self.z += other.z

    def __isub__(self, other):
        other = _toVector(other)
        self.x -= other.x
        self.y -= other.y
        self.z -= other.z

    def __imul__(self, other):
        other = _toVector(other)
        self.x *= other.x
        self.y *= other.y
        self.z *= other.z

    def __idiv__(self, other):
        other = _toVector(other)
        self.x //= other.x
        self.y //= other.y
        self.z //= other.z

    def __itruediv__(self, other):
        other = _toVector(other)
        self.x /= other.x
        self.y /= other.y
        self.z /= other.z

    def __imod__(self, other):
        other = _toVector(other)
        self.x %= other.x
        self.y %= other.y
        self.z %= other.z

    def __ipow__(self, power, modulo=None):
        self.x = pow(self.x, power, modulo)
        self.y = pow(self.y, power, modulo)
        self.z = pow(self.z, power, modulo)

    def Normalize(self):
        l = len(self)
        return self / Vector(l, l, l)

    def Negate(self):
        return Vector(-self.x, -self.y, -self.z)

    def Sum(self):
        return self.x+self.y+self.z

    def Sqrt(self):
        return Vector(sqrt(self.x), sqrt(self.y), sqrt(self.z))

def _toVector(other, default=0):
    # Boy it'd be nice if it were a vector already.
    if isinstance(other, Vector):
        return other
    # If it's dict-like...
    elif isinstance(other, collections.Mapping):
        x = y = z = default
        failcount = 0
        for key, target in ("x", x), ("y", y), ("z", z):
            try:
                target = other[key]
            except KeyError:
                failcount += 1
        if failcount >= 3:
            raise TypeError("Mapping type contains no 'x', 'y', or 'z' key. Cannot convert to Vector.")
        return Vector(x, y, z)
    # If it's iterable...
    elif isinstance(other, collections.Iterable):
        x = y = z = default
        full_elements = (x, y, z)
        num_elements = len(other)
        if num_elements < 1:
            raise TypeError("Iterable contains no elements, Cannot convert to Vector.")
        for i in range(num_elements):
            full_elements[i] = other[i]
        return Vector(x, y, z)
    # If it's a nice number...
    elif isinstance(other, int) or isinstance(other, float):
        return Vector(other, other, other)
    # Let's try direct attributes before we give up...
    else:
        x = y = z = default
        failcount = 0
        try:
            x = other.x
        except AttributeError:
            failcount += 1
        try:
            y = other.y
        except AttributeError:
            failcount += 1
        try:
            z = other.z
        except AttributeError:
            failcount += 1
        if failcount >= 3:
            raise TypeError("Object contains none of x, y, or z attributes. Cannot convert to Vector.")
        failcount = 0
        fail_attributes = list()
        if not (isinstance(other.x, int) or isinstance(other.x, float)):
            x = default
            failcount += 1
        if not (isinstance(other.y, int) or isinstance(other.y, float)):
            y = default
            failcount += 1
        if not (isinstance(other.z, int) or isinstance(other.z, float)):
            z = default
            failcount += 1
        if failcount >= 3:
            raise TypeError("None of Object's x, y, or z attributes are float or int. Cannot convert to Vector.".format(fail_attributes))
        return Vector(x, y, z)
    raise TypeError("Conversion to Vector failed, must be either Vector, Mapping, Iterable, int, float, or have x, y, or z members of int or float type in order to convert.")

def AddDeltaScalar(position, direction, dt, speed=1):
    if speed == 1:
        offset = Vector(dt, dt, dt)
    else:
        offset = Vector(dt, dt, dt) / Vector(speed, speed, speed)
    return position + (direction / offset)

def Distance(v1, v2):
    return ((v2 - v1) ** 2).Sum().Sqrt()

def Dot(v1, v2):
    return (v1 * v2).Sum()

def Cross(v1, v2):
    x1, y1, z1 = v1
    x2, y2, z2 = v2
    x = (v1.y * v2.z) - (v1.z * v2.y)
    y = (v1.z * v2.x) - (v1.x * v2.z)
    z = (v1.x * v2.y) - (v1.y * v2.x)
    return Vector(x, y, z)